# coding=utf-8
from __future__ import absolute_import, unicode_literals

import base64
import json
import random
import string

import six

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes


def to_string(obj: object, failure_replace_value: str = None, strip_all_blank: object = False) -> str:
    '''
    将对象转换为字符串
    :param obj:带转换的类型
    :type obj:
    :param failure_replace_value:转换失败时代替的类型
    :param strip_all_blank:是否移除所有空格 ,默认不移除
    :return:返回值
    :rtype: str
    '''
    try:
        if obj is None:
            return failure_replace_value

        if isinstance(obj, str):
            new_str = obj
        elif isinstance(obj, bytes):
            new_str = obj.decode(encoding='utf-8')
        elif isinstance(obj, (list, dict)):
            new_str = json.dumps(obj, ensure_ascii=False)
        else:
            new_str = str(obj)

        if strip_all_blank and new_str:
            return new_str.replace(' ', '')

        return new_str
    except:
        return failure_replace_value


def to_byte(str_or_byte):
    '''
    将str转换为bytes
    :param str_or_byte:
    :type str_or_byte:
    :return:
    :rtype:
    '''
    if not str_or_byte:
        return None
    if isinstance(str_or_byte, bytes):
        return str_or_byte
    if isinstance(str_or_byte, str):
        return str_or_byte.encode(encoding='utf-8')
    return None


class AesCrypto(object):
    def __init__(self, encoding_aes_key):
        encoding_aes_key = AesCrypto.to_binary(encoding_aes_key + '=')
        self.cipher = AesChipher(base64.b64decode(encoding_aes_key))

    def encrypt(self, text):
        tmp_list = [to_byte(text)]
        text = b''.join(tmp_list)
        text = PKCS7Encoder.encode(text)

        ciphertext = to_byte(self.cipher.encrypt(text))
        return AesCrypto.to_text(base64.b64encode(ciphertext))

    def decrypt(self, text):
        text = to_byte(text)
        plain_text = self.cipher.decrypt(base64.b64decode(text))
        padding = plain_text[-1]
        return to_string(plain_text[:-padding])

    def _get_random_strs(self, length=16):
        rule = string.ascii_letters + string.digits
        rand_list = random.sample(rule, length)
        return ''.join(rand_list)

    @staticmethod
    def to_text(value, encoding='utf-8'):
        if not value:
            return ''
        if isinstance(value, six.text_type):
            return value
        if isinstance(value, six.binary_type):
            return value.decode(encoding)
        return six.text_type(value)

    @staticmethod
    def to_binary(value, encoding='utf-8'):
        if not value:
            return b''
        if isinstance(value, six.binary_type):
            return value
        if isinstance(value, six.text_type):
            return value.encode(encoding)
        return six.binary_type(value)


class PKCS7Encoder(object):
    block_size = 32

    @classmethod
    def encode(cls, text):
        if not text: return ''
        length = len(text)
        padding_count = cls.block_size - length % cls.block_size
        if padding_count == 0:
            padding_count = cls.block_size
        padding = to_byte(chr(padding_count))
        return text + padding * padding_count

    @classmethod
    def decode(cls, decrypted):
        padding = decrypted[-1]
        if padding < 1 or padding > 32:
            padding = 0
        return decrypted[:-padding]


class AesChipher():
    def __init__(self, key):
        backend = default_backend()
        self.cipher = Cipher(
            algorithms.AES(key),
            modes.CBC(key[:16]),
            backend=backend
        )

    def encrypt(self, plaintext):
        encryptor = self.cipher.encryptor()
        return encryptor.update(plaintext) + encryptor.finalize()

    def decrypt(self, ciphertext):
        decryptor = self.cipher.decryptor()
        return decryptor.update(ciphertext) + decryptor.finalize()
